//-----------------------------------------------------------------------------
//  Copyright (C) 2011-2018, GB Research, LLC (www.gbresearch.com)
//  
//  Boost Software License - Version 1.0 - August 17th, 2003
//
//  Permission is hereby granted, free of charge, to any person or organization
//  obtaining a copy of the software and accompanying documentation covered by
//  this license (the "Software") to use, reproduce, display, distribute,
//  execute, and transmit the Software, and to prepare derivative works of the
//  Software, and to permit third-parties to whom the Software is furnished to
//  do so, all subject to the following:
//
//  The copyright notices in the Software and this entire statement, including
//  the above license grant, this restriction and the following disclaimer,
//  must be included in all copies of the Software, in whole or in part, and
//  all derivative works of the Software, unless such copies or derivative
//  works are solely in the form of machine-executable object code generated by
//  a source language processor.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
//  SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
//  FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
//  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#pragma once

#include <type_traits>
#include <utility>
#include <functional>
#include <locale>
#include <iterator>
#include <algorithm>
#include "axe_trait.h"
#include "axe_composite.h"
#include "axe_terminal_function.h"
#include "axe_iterator.h"
#include "axe_detail.h"

namespace axe
{
    inline namespace operators
    {
        //-------------------------------------------------------------------------
        // operators for composite rules
        //-------------------------------------------------------------------------

        /// binary operator& concatenation of r_and_t (only one is needed, eval order left-to-right)
        template<class R, class...Rs>
        inline
            r_and_t< Rs..., detail::enable_if_rule<R>>
            operator& (r_and_t<Rs...>&& rand, R&& r)
        {
            return std::apply([&](auto&&...rs)
            {
                return r_and_t<Rs..., std::decay_t<R>>(std::forward<decltype(rs)>(rs)..., std::forward<R>(r));
            }, std::move(rand).get());
        }

        template<class R, class...Rs>
        inline
            r_and_t< detail::enable_if_rule<R>, Rs...>
            operator& (R&& r, r_and_t<Rs...>&& rand)
        {
            return std::apply([&](auto&&...rs)
            {
                return r_and_t<std::decay_t<R>, Rs...>(std::forward<R>(r), std::forward<decltype(rs)>(rs)...);
            }, std::move(rand).get());
        }
        //-------------------------------------------------------------------------
        /// binary operator& matches rule r1 followed by rule r2
        template<class R1, class R2>
        inline
            r_and_t< detail::enable_if_rule<R1>, detail::enable_if_rule<R2>>
            operator& (R1&& r1, R2&& r2)
        {
            return r_and_t<std::decay_t<R1>, std::decay_t<R2>>(std::forward<R1>(r1), std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// binary operator| concatenation of r_or_t (only one is needed, eval order left-to-right)
        template<class R, class...Rs>
        inline
            r_or_t< Rs..., detail::enable_if_rule<R>>
            operator| (r_or_t<Rs...>&& ror, R&& r)
        {
            return std::apply([&](auto&&...rs)
            {
                return r_or_t<Rs..., std::decay_t<R>>(std::forward<decltype(rs)>(rs)..., std::forward<R>(r));
            }, std::move(ror).get());
        }

        template<class R, class...Rs>
        inline
            r_or_t< detail::enable_if_rule<R>, Rs...>
            operator| (R&& r, r_or_t<Rs...>&& ror)
        {
            return std::apply([&](auto&&...rs)
            {
                return r_or_t<std::decay_t<R>, Rs...>(std::forward<R>(r), std::forward<decltype(rs)>(rs)...);
            }, std::move(ror).get());
        }

        //-------------------------------------------------------------------------
        /// binary operator| matches rule r1 or r2
        template<class R1, class R2>
        inline
            r_or_t<detail::enable_if_rule<R1>, detail::enable_if_rule<R2>>
            operator| (R1&& r1, R2&& r2)
        {
            return r_or_t<std::decay_t<R1>, std::decay_t<R2>>(std::forward<R1>(r1), std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// operator| called with second r_fail rule, calls specified function on failure
        template<class R1, class R2>
        inline
            r_fail_t< detail::enable_if_rule<R1>, std::decay_t<R2> >
            operator| (R1&& r1, const r_fail_wrapper_t<R2>& r2)
        {
            return r_fail_t<std::decay_t<R1>, std::decay_t<R2>>(std::forward<R1>(r1), r2.get());
        }

        //-------------------------------------------------------------------------
        /// operator|| : A || B = A & B | A | B;
        template<class R1, class R2>
        inline
            r_seq_or_t<detail::enable_if_rule<R1>, detail::enable_if_rule<R2>>
            operator|| (R1&& r1, R2&& r2)
        {
            return r_seq_or_t<std::decay_t<R1>, std::decay_t<R2>>(std::forward<R1>(r1), std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// operator^ matches either the first or the second rule, but not both (r1 & !r2 | !r1 & r2)
        template<class R1, class R2>
        inline
            r_xor_t<detail::enable_if_rule<R1>, detail::enable_if_rule<R2>>
            operator^ (R1&& r1, R2&& r2)
        {
            return r_xor_t<std::decay_t<R1>, std::decay_t<R2>>(std::forward<R1>(r1), std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// operator&& matches both rules in any order
        template<class R1, class R2>
        inline
            r_unordered_and_t<detail::enable_if_rule<R1>, detail::enable_if_rule<R2>>
            operator&& (R1&& r1, R2&& r2)
        {
            return r_unordered_and_t<std::decay_t<R1>, std::decay_t<R2>>(std::forward<R1>(r1), std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// operator> if the first rule matched, the second must match or exception is thrown
        template<class R1, class R2>
        inline
            r_atomic_t<detail::enable_if_rule<R1>, detail::enable_if_rule<R2>>
            operator> (R1&& r1, R2&& r2)
        {
            return r_atomic_t<std::decay_t<R1>, std::decay_t<R2>>(std::forward<R1>(r1), std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// operator! succeeds on mismatch of the specified rule
        template<class R>
        inline
            r_not_t< detail::enable_if_rule<R> >
            operator! (R&& r)
        {
            return r_not_t<std::decay_t<R>>(std::forward<R>(r));
        }

        //-------------------------------------------------------------------------
        /// operator~ specifies an optional rule (matched 0 or 1 times)
        template<class R>
        inline
            r_opt_t< detail::enable_if_rule<R> >
            operator~ (R&& r)
        {
            return r_opt_t<std::decay_t<R>>(std::forward<R>(r));
        }

        //-------------------------------------------------------------------------
        // convenience operators
        //-------------------------------------------------------------------------
        
        /// unary operator* matches specified rule 0 or more times
        template<class R>
        inline
            r_many_t< detail::enable_if_rule<R>, r_empty >
            operator* (R&& r)
        {
            return r_many_t<std::decay_t<R>, r_empty>(std::forward<R>(r), r_empty{}, 0, -1);
        }

        /// binary operator* matches specified rule exactly N times
        template<class R>
        inline
            r_many_t< detail::enable_if_rule<R>, r_empty >
            operator* (R&& r, size_t n)
        {
            return r_many_t<std::decay_t<R>, r_empty>(std::forward<R>(r), r_empty{}, n, n);
        }

        /// binary operator* matches specified rule exactly N times
        template<class R>
        inline
            r_many_t< detail::enable_if_rule<R>, r_empty >
            operator* (size_t n, R&& r)
        {
            return r_many_t<std::decay_t<R>, r_empty>(std::forward<R>(r), r_empty{}, n, n);
        }

        /// unary operator+ matches specified rule 1 or more times
        template<class R>
        inline
            r_many_t< detail::enable_if_rule<R>, r_empty >
            operator+ (R&& r)
        {
            return r_many_t<std::decay_t<R>, r_empty>(std::forward<R>(r), r_empty{}, 1, -1);
        }

        /// operator% matches rule, followed by separator 1 or more times
        template<class R1, class R2>
        inline
            auto operator% (R1&& r1, R2&& r2) -> decltype(r_many(std::forward<R1>(r1), std::forward<R2>(r2), 1, -1))
        {
            return r_many(std::forward<R1>(r1), std::forward<R2>(r2), 1, -1);
        }

        /// binary operator- matches first rule, but not the second (r1 - r2 is the same as !r2 & r1)
        template<class R1, class R2>
        inline
            r_and_t<detail::enable_if_rule<R1, r_not_t<std::decay_t<R2>>>, detail::enable_if_rule<R2, std::decay_t<R1>>>
            operator- (R1&& r1, R2&& r2)
        {
            return r_and_t<r_not_t<std::decay_t<R2>>, std::decay_t<R1>>(r_not_t<std::decay_t<R2>>(std::forward<R2>(r2)), std::forward<R1>(r1));
        }

        /// binary operator- matches first rule, but not the second (r1 - r2 is the same as !r2 & r1)
        template<class R, class... Rs>
        inline
            r_and_t<detail::enable_if_rule<R, r_not_t<std::decay_t<R>>>, Rs...>
            operator- (r_and_t<Rs...>&& r1, R&& r2)
        {
            return std::apply([&](auto&&...r)
            {
                return r_and_t<r_not_t<std::decay_t<R>>, Rs...>(r_not_t<std::decay_t<R>>(std::forward<R>(r2)),
                    std::forward<decltype(r)>(r)...);
            }, r1.get());
        }
            
        //-------------------------------------------------------------------------
        /// AND rule for literals
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_and_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            std::enable_if_t<!AXE_IS_RULE(R2), detail::r_lit_traits_t<R2>>
        >
            operator& (R1&& r1, R2 r2)
        {
            return r_and_t<R1, detail::r_lit_traits_t<R2>>(std::forward<R1>(r1),
                static_cast<detail::r_lit_traits_t<R2>>(r2));
        }

        //-------------------------------
        template<class ...Rs, class R2>
        r_and_t<
            Rs..., std::enable_if_t<!AXE_IS_RULE(R2), detail::r_lit_traits_t<R2>>
        >
            operator& (r_and_t<Rs...>&& r1, R2 r2)
        {
            return std::apply([&](auto&&... rs)
            {
                return r_and_t<Rs..., detail::r_lit_traits_t<R2>>(std::forward<decltype(rs)>(rs)...,
                    static_cast<detail::r_lit_traits_t<R2>>(r2));
            }, std::move(r1).get());
        }

        //-------------------------------------------------------------------------
        /// AND rule for value classes
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_and_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            typename detail::r_val_traits<R2>::type
        >
            operator& (R1&& r1, R2&& r2)
        {
            return r_and_t<R1, typename detail::r_val_traits<R2>::type>(std::forward<R1>(r1),
                static_cast<typename detail::r_val_traits<R2>::type>(std::forward<R2>(r2)));
        }

        //-------------------------------
        template<class... Rs, class R2>
        r_and_t<
            Rs..., typename detail::r_val_traits<R2>::type
        >
            operator& (r_and_t<Rs...>&& r1, R2&& r2)
        {
            return std::apply([&](auto&&... rs)
            {
                return r_and_t<Rs..., typename detail::r_val_traits<R2>::type>(std::forward<decltype(rs)>(rs)...,
                    static_cast<typename detail::r_val_traits<R2>::type>(std::forward<R2>(r2)));
            }, std::move(r1).get());
        }

        //-------------------------------
        template<class R1, class R2>
        r_and_t<
            detail::r_lit_traits_t<R1>,
            std::enable_if_t<AXE_IS_RULE(R2), R2>
        >
            operator& (R1 r1, R2&& r2)
        {
            return r_and_t<detail::r_lit_traits_t<R1>, R2>(detail::r_lit_traits_t<R1>(r1),
                std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// OR rule for literals
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_or_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            std::enable_if_t<!AXE_IS_RULE(R2), detail::r_lit_traits_t<R2>>
        >
            operator| (R1&& r1, R2 r2)
        {
            return r_or_t<R1, detail::r_lit_traits_t<R2>>(std::forward<R1>(r1),
                static_cast<detail::r_lit_traits_t<R2>>(r2));
        }

        //-------------------------------------------------------------------------
        /// OR rule for value classes
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_or_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            typename detail::r_val_traits<R2>::type
        >
            operator| (R1&& r1, R2&& r2)
        {
            return r_or_t<R1, typename detail::r_val_traits<R2>::type>(std::forward<R1>(r1),
                static_cast<typename detail::r_val_traits<R2>::type>(std::forward<R2>(r2)));
        }

        //-------------------------------
        template<class R1, class R2>
        r_or_t<
            detail::r_lit_traits_t<R1>,
            std::enable_if_t<AXE_IS_RULE(R2), R2>
        >
            operator| (R1 r1, R2&& r2)
        {
            return r_or_t<detail::r_lit_traits_t<R1>, R2>(detail::r_lit_traits_t<R1>(r1),
                std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// || rule for literals
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_seq_or_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            std::enable_if_t<!AXE_IS_RULE(R2),
            detail::r_lit_traits_t<R2>>
            >
            operator|| (R1&& r1, R2 r2)
        {
            return r_seq_or_t<R1, detail::r_lit_traits_t<R2>>(std::forward<R1>(r1),
                static_cast<detail::r_lit_traits_t<R2>>(r2));
        }

        //-------------------------------------------------------------------------
        /// || rule for value classes
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_seq_or_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            typename detail::r_val_traits<R2>::type
        >
            operator|| (R1&& r1, R2&& r2)
        {
            return r_seq_or_t<R1, typename detail::r_val_traits<R2>::type>(std::forward<R1>(r1),
                static_cast<typename detail::r_val_traits<R2>::type>(std::forward<R2>(r2)));
        }

        //-------------------------------
        template<class R1, class R2>
        r_seq_or_t<
            detail::r_lit_traits_t<R1>,
            std::enable_if_t<AXE_IS_RULE(R2), R2>
        >
            operator|| (R1 r1, R2&& r2)
        {
            return r_seq_or_t<detail::r_lit_traits_t<R1>, R2>(detail::r_lit_traits_t<R1>(r1),
                std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// XOR rule for literals
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_xor_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            std::enable_if_t<!AXE_IS_RULE(R2), detail::r_lit_traits_t<R2>>
        >
            operator^ (R1&& r1, R2 r2)
        {
            return r_xor_t<R1, detail::r_lit_traits_t<R2>>(std::forward<R1>(r1),
                static_cast<detail::r_lit_traits_t<R2>>(r2));
        }

        //-------------------------------------------------------------------------
        /// XOR rule for value classes
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_xor_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            typename detail::r_val_traits<R2>::type
        >
            operator^ (R1&& r1, R2&& r2)
        {
            return r_xor_t<R1, typename detail::r_val_traits<R2>::type>(std::forward<R1>(r1),
                static_cast<typename detail::r_val_traits<R2>::type>(std::forward<R2>(r2)));
        }

        //-------------------------------
        template<class R1, class R2>
        r_xor_t<
            detail::r_lit_traits_t<R1>,
            std::enable_if_t<AXE_IS_RULE(R2), R2>
        >
            operator^ (R1 r1, R2&& r2)
        {
            return r_xor_t<detail::r_lit_traits_t<R1>, R2>(detail::r_lit_traits_t<R1>(r1),
                std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// unordered AND rule for literals
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_unordered_and_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            std::enable_if_t<!AXE_IS_RULE(R2),
            detail::r_lit_traits_t<R2>>
            >
            operator&& (R1&& r1, R2 r2)
        {
            return r_unordered_and_t<R1, detail::r_lit_traits_t<R2>>(std::forward<R1>(r1),
                static_cast<detail::r_lit_traits_t<R2>>(r2));
        }

        //-------------------------------------------------------------------------
        /// unordered AND rule for value classes
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_unordered_and_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            typename detail::r_val_traits<R2>::type
        >
            operator&& (R1&& r1, R2&& r2)
        {
            return r_unordered_and_t<R1, typename detail::r_val_traits<R2>::type>(std::forward<R1>(r1),
                static_cast<typename detail::r_val_traits<R2>::type>(std::forward<R2>(r2)));
        }

        //-------------------------------
        template<class R1, class R2>
        r_unordered_and_t<
            detail::r_lit_traits_t<R1>,
            std::enable_if_t<AXE_IS_RULE(R2), R2>
        >
            operator&& (R1 r1, R2&& r2)
        {
            return r_unordered_and_t<detail::r_lit_traits_t<R1>, R2>(detail::r_lit_traits_t<R1>(r1),
                std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// atomic rule for literals
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_atomic_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            std::enable_if_t<!AXE_IS_RULE(R2),
            detail::r_lit_traits_t<R2>>
            >
            operator> (R1&& r1, R2 r2)
        {
            return r_atomic_t<R1, detail::r_lit_traits_t<R2>>(std::forward<R1>(r1),
                static_cast<detail::r_lit_traits_t<R2>>(r2));
        }

        //-------------------------------------------------------------------------
        /// atomic rule for value classes
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_atomic_t<
            std::enable_if_t<AXE_IS_RULE(R1), R1>,
            typename detail::r_val_traits<R2>::type
        >
            operator> (R1&& r1, R2&& r2)
        {
            return r_atomic_t<R1, typename detail::r_val_traits<R2>::type>(std::forward<R1>(r1),
                static_cast<typename detail::r_val_traits<R2>::type>(std::forward<R2>(r2)));
        }

        //-------------------------------
        template<class R1, class R2>
        r_atomic_t<
            detail::r_lit_traits_t<R1>,
            std::enable_if_t<AXE_IS_RULE(R2), R2>
        >
            operator> (R1 r1, R2&& r2)
        {
            return r_atomic_t<detail::r_lit_traits_t<R1>, R2>(detail::r_lit_traits_t<R1>(r1),
                std::forward<R2>(r2));
        }

        //-------------------------------------------------------------------------
        /// operator- for literals
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_and_t<
            std::enable_if_t<AXE_IS_RULE(R1), r_not_t<detail::r_lit_traits_t<R2>>>,
            std::enable_if_t<!AXE_IS_RULE(R2)
            && !std::is_class_v<std::remove_reference_t<R2>>, R1>
        >
            operator- (R1&& r1, R2 r2)
        {
            using type = detail::r_lit_traits_t<R2>;
            return r_and_t<r_not_t<type>, R1>(r_not_t<type>(type(r2)), std::forward<R1>(r1));
        }

        //-------------------------------------------------------------------------
        /// operator- for value classes
        //-------------------------------------------------------------------------
        template<class R1, class R2>
        r_and_t<
            std::enable_if_t<AXE_IS_RULE(R1), r_not_t<typename detail::r_val_traits<R2>::type>>,
            std::enable_if_t<!AXE_IS_RULE(R2) && std::is_class_v<std::remove_reference_t<R2>>, R1>
        >
            operator- (R1&& r1, R2&& r2)
        {
            typedef typename detail::r_val_traits<R2>::type type;
            return r_and_t<r_not_t<type>, R1>(r_not_t<type>(type(std::forward<R2>(r2))), std::forward<R1>(r1));
        }

		//-------------------------------------------------------------------------
		/// operator/ is used to create constrained rule
		//-------------------------------------------------------------------------
		template<class R, class Fn>
		auto operator/ (R&& r, Fn&& fn)-> std::enable_if_t < AXE_IS_RULE(R), 
			r_constrained<std::remove_reference_t<R>, std::remove_reference_t<Fn>>>
		{
			return r_constrained(std::forward<R>(r), std::forward<Fn>(fn));
		}

    }
}
